#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/param.h>

#include <openssl/ssl.h>
#include <auth-client.h>
#include <libesmtp.h>

#include <string.h>
#include <errno.h>

#define GST_DOMAINNAME_MAXLEN 128
#define GST_COMMUNITYNAME_MAXLEN 64
#define GST_AUTHUSERID_MAXLEN 64
#define GST_AUTHPASSWD_MAXLEN 64
#define MAILBODY_LEN 16384

struct mail_server {
    int use;
    char domain_name[GST_DOMAINNAME_MAXLEN];
    u_int16_t port;
    u_int8_t encrypt; /* boolean: 0 or 1 */
    u_int8_t auth; /* boolean: 0 or 1 */
    char auth_userid[GST_AUTHUSERID_MAXLEN];
    char auth_passwd[GST_AUTHPASSWD_MAXLEN];
};

struct alarm_mail_user_entry {
    char id[128];
    char email_addr[128];
} namsik = {
    .email_addr = "Namsik.Yoon@secui.com",
};

struct config {
    int n_mail_user;
    char hostname[MAXHOSTNAMELEN];
    struct mail_server mail_server;
    struct alarm_mail_user_entry *p_mail_user;
} loaded_config = {
    .n_mail_user = 1,
    .mail_server = { 
        .auth_userid = "Namsik.Yoon@gmail.com",
        .auth_passwd = "ajfkrh123",
        .encrypt = 1,
        .auth = 1,
        .port = 587,
        .domain_name = "smtp.gmail.com",
        .use = 1,
    },
    .p_mail_user = &namsik,
};

int gDebug = 1;

#define PRN_ERROR(format, ...) \
    fprintf(stderr, format, ##__VA_ARGS__)


int authinteract(auth_client_request_t request, char **result, int fields, 
                 void *arg)
{
    int i;
    for (i = 0; i < fields; ++i) {
        if ((request[i].flags & AUTH_USER) 
            && strlen(loaded_config.mail_server.auth_userid) != 0)
            result[i] = loaded_config.mail_server.auth_userid;
        else if ((request[i].flags & AUTH_PASS)
                 && strlen(loaded_config.mail_server.auth_passwd) != 0)
            result[i] = loaded_config.mail_server.auth_passwd;
        else if (request[i].flags & AUTH_REALM)
            result[i] = (char *)"";
        else
            return 0;
    }
    return 1;
}

int handle_invalid_peer_certificate(long vfy_result)
{
    const char *k ="rare error";
    switch(vfy_result) {
    case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
        k="X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT"; break;
    case X509_V_ERR_UNABLE_TO_GET_CRL:
        k="X509_V_ERR_UNABLE_TO_GET_CRL"; break;
    case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
        k="X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE"; break;
    case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
        k="X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE"; break;
    case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
        k="X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY"; break;
    case X509_V_ERR_CERT_SIGNATURE_FAILURE:
        k="X509_V_ERR_CERT_SIGNATURE_FAILURE"; break;
    case X509_V_ERR_CRL_SIGNATURE_FAILURE:
        k="X509_V_ERR_CRL_SIGNATURE_FAILURE"; break;
    case X509_V_ERR_CERT_NOT_YET_VALID:
        k="X509_V_ERR_CERT_NOT_YET_VALID"; break;
    case X509_V_ERR_CERT_HAS_EXPIRED:
        k="X509_V_ERR_CERT_HAS_EXPIRED"; break;
    case X509_V_ERR_CRL_NOT_YET_VALID:
        k="X509_V_ERR_CRL_NOT_YET_VALID"; break;
    case X509_V_ERR_CRL_HAS_EXPIRED:
        k="X509_V_ERR_CRL_HAS_EXPIRED"; break;
    case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
        k="X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD"; break;
    case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
        k="X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD"; break;
    case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
        k="X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD"; break;
    case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
        k="X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD"; break;
    case X509_V_ERR_OUT_OF_MEM:
        k="X509_V_ERR_OUT_OF_MEM"; break;
    case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
        k="X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"; break;
    case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
        k="X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN"; break;
    case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
        k="X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"; break;
    case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
        k="X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"; break;
    case X509_V_ERR_CERT_CHAIN_TOO_LONG:
        k="X509_V_ERR_CERT_CHAIN_TOO_LONG"; break;
    case X509_V_ERR_CERT_REVOKED:
        k="X509_V_ERR_CERT_REVOKED"; break;
    case X509_V_ERR_INVALID_CA:
        k="X509_V_ERR_INVALID_CA"; break;
    case X509_V_ERR_PATH_LENGTH_EXCEEDED:
        k="X509_V_ERR_PATH_LENGTH_EXCEEDED"; break;
    case X509_V_ERR_INVALID_PURPOSE:
        k="X509_V_ERR_INVALID_PURPOSE"; break;
    case X509_V_ERR_CERT_UNTRUSTED:
        k="X509_V_ERR_CERT_UNTRUSTED"; break;
    case X509_V_ERR_CERT_REJECTED:
        k="X509_V_ERR_CERT_REJECTED"; break;
    }
    PRN_ERROR("SMTP_EV_INVALID_PEER_CERTIFICATE: %ld: %s\n", vfy_result, k);
    return 1; /* Accept the problem */
}

void monitor_cb(const char *buf, int buflen, int writing, void *arg)
{
    FILE *fp = arg;

    if (writing == SMTP_CB_HEADERS) {
        fputs("H: ", fp);
        if (fwrite(buf, 1, buflen, fp) != 1)
            PRN_ERROR("fwrite: failed(%s)\n", strerror(errno));
        return;
    }

    fputs(writing ? "C: " : "S: ", fp);
    if (fwrite(buf, 1, buflen, fp) != (unsigned)buflen)
        PRN_ERROR("fwrite: failed(%s)\n", strerror(errno));
    if (buf[buflen - 1] != '\n')
        fputc('\n', fp);
}

void event_cb (smtp_session_t session, int event_no, void *arg,...)
{
    va_list alist;
    int *ok;

    va_start(alist, arg);
    switch(event_no) {
    case SMTP_EV_CONNECT:
    case SMTP_EV_MAILSTATUS:
    case SMTP_EV_RCPTSTATUS:
    case SMTP_EV_MESSAGEDATA:
    case SMTP_EV_MESSAGESENT:
    case SMTP_EV_DISCONNECT: break;
    case SMTP_EV_WEAK_CIPHER: {
        int bits;
        bits = va_arg(alist, long); ok = va_arg(alist, int*);
        PRN_ERROR("SMTP_EV_WEAK_CIPHER, bits=%d - accepted.\n", bits);
        *ok = 1; break;
    }
    case SMTP_EV_STARTTLS_OK:
        PRN_ERROR("SMTP_EV_STARTTLS_OK - TLS started here."); break;
    case SMTP_EV_INVALID_PEER_CERTIFICATE: {
        long vfy_result;
        vfy_result = va_arg(alist, long); ok = va_arg(alist, int*);
        *ok = handle_invalid_peer_certificate(vfy_result);
        break;
    }
    case SMTP_EV_NO_PEER_CERTIFICATE: {
        ok = va_arg(alist, int*);
        PRN_ERROR("SMTP_EV_NO_PEER_CERTIFICATE - accepted.");
        *ok = 1; break;
    }
    case SMTP_EV_WRONG_PEER_CERTIFICATE: {
        ok = va_arg(alist, int*);
        PRN_ERROR("SMTP_EV_WRONG_PEER_CERTIFICATE - accepted.");
        *ok = 1; break;
    }
    case SMTP_EV_NO_CLIENT_CERTIFICATE: {
        ok = va_arg(alist, int*);
        PRN_ERROR("SMTP_EV_NO_CLIENT_CERTIFICATE - accepted.");
        *ok = 1; break;
    }
    default:
        PRN_ERROR("Got event: %d - ignored.\n", event_no);
    }
    va_end(alist);
}

/* Callback to prnt the recipient status */
void print_recipient_status (smtp_recipient_t recipient, 
                             const char *mailbox, void *arg)
{
    const smtp_status_t *status;

    status = smtp_recipient_status (recipient);
    PRN_ERROR("%s: %d %s", mailbox, status->code, status->text);
}

#define MAILBODY_LEN 16384
int sendmail(char *subject, char *description)
{
    PRN_ERROR("send mail\n");
    int retval = 0, res;
    smtp_session_t session = NULL;
    smtp_message_t message = NULL;
    smtp_recipient_t recipient = NULL;
    auth_context_t authctx = NULL;
    const smtp_status_t *p_status = NULL;

    char mailbody[MAILBODY_LEN];
    char server_info[GST_DOMAINNAME_MAXLEN + 6];
    char mail_from[MAXHOSTNAMELEN + 20];

    session = smtp_create_session();
    if (session == NULL) {
        PRN_ERROR("session == NULL\n");
        retval = 1;
        goto done;
    }
    message = smtp_add_message(session);
    if (message == NULL) {
        PRN_ERROR("message == NULL\n");
        retval = 2;
        goto done;
    }

    if (loaded_config.mail_server.encrypt != 0)
        smtp_starttls_enable(session, Starttls_ENABLED);

    if (gDebug)
        smtp_set_monitorcb(session, monitor_cb, stdout, 1);

    snprintf(server_info, GST_DOMAINNAME_MAXLEN + 6,
             "%s:%d", loaded_config.mail_server.domain_name,
             loaded_config.mail_server.port);
    PRN_ERROR("server_info = %s\n", server_info);

    snprintf(mail_from, MAXHOSTNAMELEN + 20, "alarm_mailer@%s", 
             loaded_config.hostname);

    res = smtp_set_server(session, server_info);
    if (res == 0) {
        PRN_ERROR("res == 0 server_info = %s\n", server_info);
        retval = 3;
        goto done;
    }

    if (loaded_config.mail_server.auth != 0) {
        authctx = auth_create_context();

        auth_set_mechanism_flags(authctx, AUTH_PLUGIN_PLAIN, 0);
        auth_set_interact_cb(authctx, authinteract, NULL);

        smtp_set_eventcb(session, event_cb, NULL);
        smtp_auth_set_context(session, authctx);
    }
    
    /* Set the reverse path for the mail envelope. (NULL is OK) */
    smtp_set_reverse_path(message, mail_from);
    
    /* RFC 2822 doesn't require recipient headers but a To: header would
       be nice to have if not present. */
    smtp_set_header(message, "To", NULL, NULL);

    int i;
    for (i = 0; i < loaded_config.n_mail_user; ++i) {
        recipient = smtp_add_recipient(message, 
                                       loaded_config.p_mail_user[i].email_addr);
        PRN_ERROR("recipient %s added\n",
                  loaded_config.p_mail_user[i].email_addr);
    }

    snprintf(mailbody, MAILBODY_LEN - 1, 
             "Subject: %s\r\n\r\n"
             "%s\r\n" "\r\n.\r\n", subject, description);
    PRN_ERROR("mailbody = [%s]", mailbody);
    smtp_set_message_str(message, mailbody);

    res = smtp_start_session(session);
    PRN_ERROR("smtp_start_session returns %d\n", res);
    if (res == 0) {
        char buf[128];
        PRN_ERROR("SMTP server problem %s\n", 
                  smtp_strerror(smtp_errno(), buf, sizeof(buf)));
        retval = 4;
    } else {
        p_status = smtp_message_transfer_status(message);
        PRN_ERROR("%d %s", p_status->code, 
                  (p_status->text != NULL) ? p_status->text : "\n");
        smtp_enumerate_recipients(message, print_recipient_status, NULL);
    }

 done:
    if (session != NULL)
        smtp_destroy_session(session);
    if (loaded_config.mail_server.auth != 0) {
        auth_destroy_context(authctx);
    }

    return retval;
}

int main(void)
{
    int r = sendmail("Test", "Hello");
    return 0;
}
